/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						       combat_window.cpp

	$Header: /game/combat_window.cpp $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "combat_window.h"

#include <cassert>

#include "abstract_combat_object.h"
#include "adventure_frame.h"
#include "adventure_map.h"
#include "adventure_map_window.h"
#include "adventure_sounds.h"
#include "army.h"
#include "army_list.h"
#include "battlefield.h"
#include "battlefield_metrics.h"
#include "battlefield_preset_map_cache.h"
#include "battlefield_terrain_map.h"
#include "battlefield_window.h"
#include "bitmap_group_cache.h"
#include "bitmap_layer_window.h"
#include "bound_handler.h"
#include "button_cache.h"
#include "button.h"
#include "cheats.h"
#include "combat_actor_action.h"
#include "combat_actor_model_cache.h"
#include "combat_context.h"
#include "combat_creature.h"
#include "combat_creature_icon.h"
#include "combat_label.h"
#include "combat_start_position.h"
#include "convert_16_bit.h"
#include "creature.h"
#include "creature_array.h"
#include "creature_traits.h"
#include "creature_type.h"
#include "debug_message.h"
#include "dialog_combat_options.h"
#include "direction.h"
#include "elements_of.h"
#include "enum_operations.h"
#include "external_string.h"
#include "formation_type.h"
#include "game_application.h"
#include "interface_help.h"
#include "main_menu.h"
#include "map_rect.h"
#include "memory_window.h"
#include "music.h"
#include "obstacle_type.h"
#include "options.h"
#include "ownable_garrisonable_adv_object.h"
#include "paint_surface.h"
#include "pixel_24.h"
#include "platform.h"
#include "player.h"
#include "player_color.h"
#include "random.h"
#include "replace_keywords.h"
#include "report_combat_results.h"
#include "resource.h"
#include "saved_combat.h"
#include "screen_point.h"
#include "shared_ptr.h"
#include "simple_dialog.h"
#include "sound.h"
#include "sound_cache.h"
#include "spell_icon_window.h"
#include "spell_properties.h"
#include "standard_fonts.h"
#include "stationary_combat_object.h"
#include "text_window.h"
#include "town.h"
#include "town_image_level.h"
#include "town_list.h"
#include "uncopyable.h"

extern t_button_cache g_ok_button;

namespace 
{
	// hotkey resource strings
	t_external_string const k_hot_key_cast_spell( "cast_spell_combat.hot_keys" );	// C
	t_external_string const k_hot_key_defend( "defend.hot_keys" );					// D
	t_external_string const k_hot_key_wait( "wait.hot_keys" );						// W
	t_external_string const k_hot_key_auto_combat( "auto_combat.hot_keys" );		// A
	t_external_string const k_hot_key_combat_options( "combat_options.hot_keys" );	// O
	t_external_string const k_hot_key_retreat( "retreat.hot_keys" );				// R
	t_external_string const k_hot_key_surrender( "surrender.hot_keys" );			// S
	t_external_string const k_hot_key_grid_toggle( "combat_grid_toggle.hot_keys" ); // G

	t_counted_ptr<t_dialog_cheat> g_cheats_window;

	enum t_combat_type
	{
		k_ship_combat_attacker,
		k_ship_combat_defender,
		k_ship_combat_single,
		k_bridge_normal_combat,
		k_bridge_evil_combat
	};

	std::string	create_ship_resource_name( t_combat_type combat_type , t_town_type  town)
	{
		std::string full_resource_name;

		//create the resource name from the given parameters to make it more flexbile

		switch (town)
		{
		case k_town_life:
			full_resource_name += "Life.";
			break;
		case k_town_order:	
			full_resource_name += "Order.";
			break;
		case k_town_death:	
			full_resource_name += "Death.";
			break;
		case k_town_chaos:	
			full_resource_name += "Chaos.";
			break;
		case k_town_nature:	
			full_resource_name += "Nature.";
			break;
		case k_town_might:	
			full_resource_name += "Might.";
			break;
		default:
			full_resource_name += "netural.";
			break;
		}
		
		switch (combat_type)
		{
		case k_ship_combat_attacker:
			full_resource_name += "lower";
			break;
		case k_ship_combat_defender:	
			full_resource_name += "upper";
			break;
		case k_ship_combat_single:	
			full_resource_name += "single";
			break;
		default:
			full_resource_name += "single";
			break;
		}
		
		
		return full_resource_name;
	}

	std::string		create_bridge_resource_name( t_combat_type combat_type )
	{
		std::string full_resource_name;
		
		//create the resource name from the given parameters to make it more flexbile
		
		full_resource_name += "Bridge.";
		
		switch( combat_type )
		{
		case k_bridge_normal_combat:
			full_resource_name += "normal.single";
			break;
		case k_bridge_evil_combat:
			full_resource_name += "evil.single";
			break;
		default:
			full_resource_name += "normal.single";
			break;
		}

		return full_resource_name;
	}

	static t_battlefield_preset_map_cache	const k_town_life_boat_battlefield_attacker(  create_ship_resource_name( k_ship_combat_attacker , k_town_life) );
	static t_battlefield_preset_map_cache	const k_town_order_boat_battlefield_attacker( create_ship_resource_name( k_ship_combat_attacker , k_town_order) );
	static t_battlefield_preset_map_cache	const k_town_death_boat_battlefield_attacker( create_ship_resource_name( k_ship_combat_attacker , k_town_death) );
	static t_battlefield_preset_map_cache	const k_town_chaos_boat_battlefield_attacker( create_ship_resource_name( k_ship_combat_attacker , k_town_chaos) );
	static t_battlefield_preset_map_cache	const k_town_nature_boat_battlefield_attacker(create_ship_resource_name( k_ship_combat_attacker , k_town_nature) );
	static t_battlefield_preset_map_cache	const k_town_might_boat_battlefield_attacker( create_ship_resource_name( k_ship_combat_attacker , k_town_might) );

	static t_battlefield_preset_map_cache	const k_town_life_boat_battlefield_defender(  create_ship_resource_name( k_ship_combat_defender , k_town_life) );
	static t_battlefield_preset_map_cache	const k_town_order_boat_battlefield_defender( create_ship_resource_name( k_ship_combat_defender , k_town_order) );
	static t_battlefield_preset_map_cache	const k_town_death_boat_battlefield_defender( create_ship_resource_name( k_ship_combat_defender , k_town_death) );
	static t_battlefield_preset_map_cache	const k_town_chaos_boat_battlefield_defender( create_ship_resource_name( k_ship_combat_defender , k_town_chaos) );
	static t_battlefield_preset_map_cache	const k_town_nature_boat_battlefield_defender(create_ship_resource_name( k_ship_combat_defender , k_town_nature) );
	static t_battlefield_preset_map_cache	const k_town_might_boat_battlefield_defender( create_ship_resource_name( k_ship_combat_defender , k_town_might) );

	static t_battlefield_preset_map_cache	const k_town_life_single_boat_battlefield(   create_ship_resource_name( k_ship_combat_single , k_town_life) );
	static t_battlefield_preset_map_cache	const k_town_order_single_boat_battlefield(  create_ship_resource_name( k_ship_combat_single , k_town_order) );
	static t_battlefield_preset_map_cache	const k_town_death_single_boat_battlefield(  create_ship_resource_name( k_ship_combat_single , k_town_death) );
	static t_battlefield_preset_map_cache	const k_town_chaos_single_boat_battlefield(  create_ship_resource_name( k_ship_combat_single , k_town_chaos) );
	static t_battlefield_preset_map_cache	const k_town_nature_single_boat_battlefield( create_ship_resource_name( k_ship_combat_single , k_town_nature) );
	static t_battlefield_preset_map_cache	const k_town_might_single_boat_battlefield(  create_ship_resource_name( k_ship_combat_single , k_town_might) );

	static t_battlefield_preset_map_cache	const k_bridge_normal_battlefield( create_bridge_resource_name(k_bridge_normal_combat) );
	static t_battlefield_preset_map_cache	const k_bridge_evil_battlefield( create_bridge_resource_name(k_bridge_evil_combat) );
};

// -----------------------------------------------------------------------
// re-arrange neutral defending army to suit player-owned attacker.
// -----------------------------------------------------------------------
static bool back_row_creature( t_creature_stack const& creature )
{
	if (creature.has_ability( k_ability_ranged ))
		return true;
	
	t_creature_type creature_type = creature.get_creature_type();

	if (creature_type == k_creature_none)
		return true;

	t_spell spell = get_default_spell( creature_type );

	if (spell == k_spell_none)
		return false;

	return is_damage( spell );
}

static void auto_arrange( t_creature_array& army, t_creature_array const& enemy_army )
{
	static int const slot_order[2][t_creature_array::k_size] = 
	{ {  2, 4, 0, 6, 3, 1, 5 }, { 3, 1, 5, 2, 4, 0, 6 } };

	t_creature_array new_army( &army );
	int              i;
	int              j;
	int              slot;
	bool             ranged;
	int				 stack_count;
	int				 limit;
	int				 number;
	int					creature_types[2];
	t_creature_type	 creature_type;
	bool             used[t_creature_array::k_size];

	for (i = 0; i < 2; ++i)
		creature_types[i] = 0;
	// consolidate and place in preferred slots
	new_army.consolidate();
	for (i = 0; i < t_creature_array::k_size; i++)
	{
		if (new_army[i].get_number() == 0)
			continue;
		
		ranged = back_row_creature( new_army[i] );
		++creature_types[ranged];
		for (j = 0; j < t_creature_array::k_size; j++)
		{
			slot = slot_order[ranged][j];
			if (army[slot].can_add( new_army[i] ))
				break;
		}
		army.add( &new_army[i], slot );
	}

	int					splittable_stacks = 0;
	int					total_stacks = 0;
	for (i = 0; i < t_creature_array::k_size; ++i)
	{
		if (army[i].get_number() == 0)
			continue;
		++total_stacks;
		if (army[i].get_number() > 0)
			++splittable_stacks;
	}

	// count enemy stacks
	int enemy_stacks = 0;

	for (i = 0; i < t_creature_array::k_size; ++i)
		if (enemy_army[i].get_number() > 0)
			++enemy_stacks;

	int split_limit;

	split_limit = enemy_stacks - total_stacks;

	if (split_limit <= 0)
		return;
	if (splittable_stacks == 0)
		return;

	// split stacks
	memset( used, 0, sizeof( used ));
	for (i = 0; i < t_creature_array::k_size; i++)
	{
		if (used[i])
			continue;
		if (army[i].get_number() == 0)
			continue;
		ranged = back_row_creature( army[i] );
		if (army[i].get_number() > 1)
		{
			if (ranged)
				limit = 3;
			else
				limit = 4;
			if (limit > split_limit + 1)
				limit = split_limit + 1;
			stack_count = 1;
			for (j = 0; j < limit; j++)
			{
				slot = slot_order[ranged][j];
				if (slot != i && army[slot].can_add( army[i] ))
					stack_count++;
			}
			if (stack_count > army[i].get_number())
				stack_count = army[i].get_number();

			limit = limit / creature_types[ranged];
			if (stack_count > limit)
				stack_count = limit;

			j = 0;
			creature_type = army[i].get_creature_type();
			while (stack_count-- > 1)
			{
				do
				{
					slot = slot_order[ranged][j];
					j++;
				} while (slot == i || !army[slot].can_add( army[i] ));
				number = army[i].get_number() / (stack_count + 1);
				army.add( new t_creature( creature_type, number ), slot );
				used[slot] = true;
				army.remove( number, i );
				--split_limit;
			} 
		}
		--creature_types[ranged];
		used[i] = true;
	}
}

t_battlefield_preset_map_in_game_ptr			get_battlefield_preset_map_resource_attacker(  t_town_type	boat_type )
{
	t_battlefield_preset_map_in_game_ptr	resource;

	switch (boat_type)
	{
	case k_town_life:
		resource = k_town_life_boat_battlefield_attacker.get();
		break;
	case k_town_order:	
		resource = k_town_order_boat_battlefield_attacker.get();
		break;
	case k_town_death:	
		resource = k_town_death_boat_battlefield_attacker.get();
		break;
	case k_town_chaos:	
		resource = k_town_chaos_boat_battlefield_attacker.get();
		break;
	case k_town_nature:	
		resource = k_town_nature_boat_battlefield_attacker.get();
		break;
	case k_town_might:	
		resource = k_town_might_boat_battlefield_attacker.get();
		break;
	default:
		break;
	}
	
	return resource;
}


t_battlefield_preset_map_in_game_ptr			get_battlefield_preset_map_resource_defender(  t_town_type	boat_type )
{
	t_battlefield_preset_map_in_game_ptr	resource;

	switch (boat_type)
	{
	case k_town_life:
		resource = k_town_life_boat_battlefield_defender.get();
		break;
	case k_town_order:	
		resource = k_town_order_boat_battlefield_defender.get();
		break;
	case k_town_death:	
		resource = k_town_death_boat_battlefield_defender.get();
		break;
	case k_town_chaos:	
		resource = k_town_chaos_boat_battlefield_defender.get();
		break;
	case k_town_nature:	
		resource = k_town_nature_boat_battlefield_defender.get();
		break;
	case k_town_might:	
		resource = k_town_might_boat_battlefield_defender.get();
		break;
	default:
		break;
	}
	
	return resource;
}

t_battlefield_preset_map_in_game_ptr			get_battlefield_preset_map_resource_single(  t_town_type	boat_type )
{
	t_battlefield_preset_map_in_game_ptr	resource;

	switch (boat_type)
	{
	case k_town_life:
		resource = k_town_life_single_boat_battlefield.get();
		break;
	case k_town_order:	
		resource = k_town_order_single_boat_battlefield.get();
		break;
	case k_town_death:	
		resource = k_town_death_single_boat_battlefield.get();
		break;
	case k_town_chaos:	
		resource = k_town_chaos_single_boat_battlefield.get();
		break;
	case k_town_nature:	
		resource = k_town_nature_single_boat_battlefield.get();
		break;
	case k_town_might:	
		resource = k_town_might_single_boat_battlefield.get();
		break;
	default:
		break;
	}
	
	return resource;
}

t_battlefield_preset_map_in_game_ptr			get_battlefield_preset_map_resource_bridge( t_combat_type combat_type )
{
	t_battlefield_preset_map_in_game_ptr resource;

	switch( combat_type )
	{
	case k_bridge_normal_combat:
		resource = k_bridge_normal_battlefield.get();
		break;
	case k_bridge_evil_combat:
		resource = k_bridge_evil_battlefield.get();
		break;
	default:
		resource = k_bridge_normal_battlefield.get();
		break;
	}
	
	return resource;
}


static void get_preset_maps( t_preset_battle_map_array& boat_resources,
							 t_creature_array&			attacker,
							 t_creature_array&			defender,
							 t_adv_map_point const&		location )
{
	t_battlefield_preset_map_in_game_ptr	attackers_boat_resource;
	t_battlefield_preset_map_in_game_ptr	defenders_boat_resource;
	t_town_type								attacker_boat = k_town_none;
	t_town_type								defender_boat = k_town_none;
	bool									boat_combat   = false;

	if (attacker.is_boat() && defender.is_boat() )
	{
		t_town_type		attacker_boat = attacker.get_boat_type();
		t_town_type		defender_boat = defender.get_boat_type();

		attackers_boat_resource = get_battlefield_preset_map_resource_attacker ( attacker_boat );
		defenders_boat_resource = get_battlefield_preset_map_resource_defender ( defender_boat );
	}

	if (attacker.is_boat() ==  true && defender.is_boat() ==  false )
	{
		attackers_boat_resource 
			= get_battlefield_preset_map_resource_single ( attacker.get_boat_type() );
	}
	else if (attacker.is_boat() ==  false && defender.is_boat() ==  true )
	{
		defenders_boat_resource 
			= get_battlefield_preset_map_resource_single ( defender.get_boat_type());
	}

	if (defenders_boat_resource.get() != NULL)
		boat_resources.push_back(defenders_boat_resource);
	
	if (attackers_boat_resource.get() != NULL)
		boat_resources.push_back(attackers_boat_resource);

	if (boat_resources.size() != 0)
		return;
		
	////////////////////////////////////////////////////////////////////////////
	//handle bridge combat
	t_battlefield_preset_map_in_game_ptr bridge_resource;

	if (location.on_bridge)
	{
		//determine which combat bridge type and pass that parameter to the get_battlefield_preset_map_resource_bridge
		bridge_resource = get_battlefield_preset_map_resource_bridge( k_bridge_normal_combat );
		if (bridge_resource.get() != NULL)
			boat_resources.push_back(bridge_resource);
	}
};

// -----------------------------------------------------------------------
// t_combat_window members
// -----------------------------------------------------------------------

// -----------------------------------------------------------------------
// t_combat_window class
// -----------------------------------------------------------------------
static t_bitmap_group_cache const k_bitmaps_800( "combat.0800" );
static t_bitmap_group_cache const k_bitmaps_1024( "combat.1024" );
static t_bitmap_group_cache const k_bitmaps_1280( "combat.1280" );
static t_sound_cache        const k_start_sound( "combat.start" );

static t_sound_cache const k_music[] =
{
	"combat.music.1",
	"combat.music.2",
	"combat.music.3",
	"combat.music.4",
	"combat.music.5",
	"combat.music.6"
};

// update all creature's active spell effects
static void update_active_spell_effects( t_battlefield& battlefield )
{
	t_combat_creature_list::iterator	index = battlefield.creatures_begin();
	t_combat_creature_list::iterator	end = battlefield.creatures_end();
	
	for (; index != end; ++index)
	{
		(*index)->change_active_spell_effect();
	}
}

t_combat_window::t_combat_window( t_saved_combat&	saved_combat, t_window* parent )				 
				: t_window( k_no_transparency, parent )
{
	t_combat_context*	context = saved_combat.get_context();
	t_battlefield&		battlefield = *saved_combat.get_battlefield();

	m_context = context;
	m_terrain_map = saved_combat.get_terrain_map();
	m_battlefield = saved_combat.get_battlefield();
	m_battlefield->set_combat_window( this );
	initialize();

	t_preset_battle_map_array	boat_resources;

	get_preset_maps( boat_resources, *context->get_attacker(), *context->get_defender(),
					 context->get_location() );

	bool boat_combat = context->get_attacker()->is_boat() || context->get_defender()->is_boat();

	m_battlefield_window = new t_battlefield_window( t_screen_point(0,0), this, m_battlefield, 
													 *m_terrain_map, boat_resources,
													 !boat_combat );
	m_battlefield_window->move_to_back();
	update_active_spell_effects( *m_battlefield );

	m_battlefield_window->set_grid_shading( get_show_combat_grid() );
}

t_combat_window::t_combat_window( t_combat_context* context, t_window* parent )
				: t_window( k_no_transparency, parent )
{
	m_context = context;
	m_terrain_map = new t_battlefield_terrain_map;

	create_battlefield();
	place_combatants();

	m_battlefield_window->set_grid_shading( get_show_combat_grid() );
}

void t_combat_window::initialize( )
{
	t_creature_array*		attacker = m_context->get_attacker();
	t_creature_array*		defender = m_context->get_defender();
	t_player*				attacking_player = attacker->get_owner();
	t_player*				defending_player = defender->get_owner();
	t_adventure_map&		adventure_map = *m_context->get_adventure_map();
	t_adventure_frame*		adventure_frame	= m_context->get_adventure_frame();
	t_screen_rect			rect   = get_parent()->get_client_rect();
	t_window*				background_window;
	t_window*				window;
	t_bitmap_layer const*	layer;
	t_screen_point			zero(0,0);
	t_sound_ptr				music;

	// fade out the active sounds list
	close_active_sounds();

	adventure_frame->get_map_window()->enable_animation( false );
	t_game_application::set_cursor_visible( false );
	redraw_windows();

	play_music( k_music[random( ELEMENTS_OF( k_music ) )] );
	m_result = k_result_unknown;

	switch (rect.width())
	{
		case 800:
			m_bitmaps = k_bitmaps_800.get();
			break;
		case 1024:
			m_bitmaps = k_bitmaps_1024.get();
			break;
		case 1280:
			m_bitmaps = k_bitmaps_1280.get();
			break;
	}

	m_ready_creature = NULL;

	layer = m_bitmaps->find( "background" );
	background_window = new t_bitmap_layer_window( layer, zero, this );
	layer = m_bitmaps->find( "border" );
	window = new t_bitmap_layer_window( layer, zero, this );

	create_buttons( background_window );
	create_displays( background_window );

	init( rect );
	t_game_application::set_cursor_visible( true );

	move_memory_window_to_front();


}

// -----------------------------------------------------------------------
// t_combat_window class
// -----------------------------------------------------------------------
t_combat_window::~t_combat_window()
{
	
}

void t_combat_window::start()
{
	m_battlefield->update_state();
}
// -----------------------------------------------------------------------
// t_combat_window class
// -----------------------------------------------------------------------
extern t_external_string const k_text_do_you_want_to_quit;

void t_combat_window::quit()
{
	// This works, but it'd be nicer probably to make t_adventure_frame::quit() public
	// and call that to get the save & close option.
	assert( m_context );
	assert( m_context->get_adventure_frame() );
	if (yes_no_dialog( k_text_do_you_want_to_quit ))
	{
		m_context->get_adventure_frame()->do_quit( NULL );
	}
}

// ----------------------------------------------------
// move creature in a direction until it can't be placed.
// ----------------------------------------------------
static bool shift_creature( t_battlefield& battlefield, t_combat_creature& creature,
						    t_map_point_2d const& delta, int step_limit = 100 )
{
	t_map_point_2d start_position;
	t_map_point_2d point;

	start_position = creature.get_cell_position();
	point = start_position;
	do
	{
		point += delta;
	} while (battlefield.can_place( creature, point ) && step_limit--);
	point -= delta;
	if (point == start_position)
		return false;
	point <<= k_battlefield_subcell_shift;
	battlefield.move_object( creature, point );
	return true;
}


// ----------------------------------------------------
// reposition shooter against wall
// ----------------------------------------------------
static void move_away_from_wall( t_battlefield& battlefield, t_combat_creature& creature )
{
	t_map_point_2d start_position;
	t_map_point_2d point;

	shift_creature( battlefield, creature, t_map_point_2d(1,0) );
	start_position = creature.get_cell_position();
	point = start_position;
	point.row -= k_wall_safe_distance + 1;
	while (!battlefield.can_place( creature, point ) && point != start_position)
		++point.row;
	if (point.row == start_position.row)
		return;
	point <<= k_battlefield_subcell_shift;
	battlefield.move_object( creature, point );
}

// ----------------------------------------------------
// shift one creature toward another
// ----------------------------------------------------
static bool shift_toward( t_battlefield& battlefield, t_combat_creature* creature,
						  t_combat_creature const* target, int step_limit = 100 )
{
	if (creature == 0 || target == 0)
		return false;
	
	t_map_point_2d delta;

	delta = target->get_footprint_center() - creature->get_footprint_center();
	delta /= k_battlefield_subcells_per_cell;
	if (delta.row < 0)
		delta.row = -1;
	if (delta.row > 0)
		delta.row = 1;
	if (delta.column < 0)
		delta.column = -1;
	if (delta.column > 0)
		delta.column = 1;
	return shift_creature( battlefield, *creature, delta, step_limit );
}
	
// ----------------------------------------------------
// re-arrange tight formation
// ----------------------------------------------------
static void tighten_tight_formation( t_battlefield& battlefield, t_combat_creature** creatures )
{
	// squeeze positions 2 and 4 together.
	while (shift_toward( battlefield, creatures[2], creatures[4], 1 )
		   && shift_toward( battlefield, creatures[4], creatures[2], 1 ))
		;
	// move position 0 toward position 2
	shift_toward( battlefield, creatures[0], creatures[2] );
	shift_toward( battlefield, creatures[6], creatures[4] );
	shift_toward( battlefield, creatures[1], creatures[3] );
	shift_toward( battlefield, creatures[5], creatures[3] );
}

static void tighten_square_formation( t_battlefield& battlefield, t_combat_creature** creatures )
{
	shift_toward( battlefield, creatures[2], creatures[3] );
	shift_toward( battlefield, creatures[0], creatures[2] );
	shift_toward( battlefield, creatures[4], creatures[2] );
	shift_toward( battlefield, creatures[1], creatures[3] );
	shift_toward( battlefield, creatures[5], creatures[3] );
	shift_toward( battlefield, creatures[6], creatures[3] );
	shift_toward( battlefield, creatures[0], creatures[1] );
	shift_toward( battlefield, creatures[4], creatures[5] );
}

// ----------------------------------------------------
// return the terrain map
// ----------------------------------------------------
t_battlefield_terrain_map* t_combat_window::get_terrain_map()
{
	return m_terrain_map;
}


// ----------------------------------------------------
// frame window for combat map
// setup and run combat with given armies
// ----------------------------------------------------
static void place_army( t_battlefield& battlefield, t_creature_array& army, 
					    t_formation_type formation, t_player_color color, bool defender,
						t_player const* owner )
{
	double						scale = battlefield.get_model_scale();
	int							i;
	t_combat_creature_ptr		actor;
	int							footprint_size;
	t_map_point_2d				position;
	t_map_point_3d				label_position;
	int							frame_count;
	t_stationary_combat_object* tower;
	t_combat_creature*			creatures[t_creature_array::k_size];

	for (i = 0; i < t_creature_array::k_size; i++)
	{
		creatures[i] = 0;
		if (army[i].get_number() == 0)
			continue;
		if (army[i].get_wounds() >= army[i].get_hit_points()
			|| army[i].is_dead())
			continue;

		actor = new t_combat_creature( battlefield, &army[i], i, &army, scale, defender, owner );
		creatures[i] = actor;
		footprint_size = actor->get_footprint_size();
		position = k_combat_start_position[formation][defender][i]
			       - t_map_point_2d( footprint_size - 1, footprint_size - 1 ) / 2;
		assert( !battlefield.off_map( *actor, position ) );
		assert( battlefield.can_place( *actor, position, 0 ));
		battlefield.place_object( actor, position << k_battlefield_subcell_shift );
		actor->set_wait_animation();
		frame_count = actor->get_frame_count();
		if (frame_count > 1)
			battlefield.set_current_frame_num( *actor, random( frame_count ));
		tower = battlefield.get_tower( actor->get_cell_position(), actor->get_footprint() );
		if (tower != 0)
			actor->place_in_tower();
		else if (formation >= k_formation_fort && formation <= k_formation_castle)
		{
			if ( back_row_creature( army[i] ) )
				shift_creature( battlefield, *actor, t_map_point_2d(1,0) );
			else
				move_away_from_wall( battlefield, *actor );
		}
	}
	switch (formation)
	{
		case k_formation_tight:
			tighten_tight_formation( battlefield, creatures );
			break;
		
		case k_formation_square:
			tighten_square_formation( battlefield, creatures );
			break;
	}
}

// ----------------------------------------------------
// preferred terrain textures
// ----------------------------------------------------
static int const k_preferred_texture[][3] = 
{
	{ k_terrain_grass,			1, 0 },
	{ k_terrain_rough,			0, 0 },
	{ k_terrain_swamp,			1, 0 },
	{ k_terrain_lava,			0, 0 },
	{ k_terrain_snow,			0, 1 },
	{ k_terrain_sand,			0, 0 },
	{ k_terrain_dirt,			0, 1 },
	{ k_terrain_subterranean,	0, 1 },
};

static int get_preferred_texture( t_terrain_type terrain )
{
	int texture[k_basic_terrain_count];
	bool initialized = false;

	if (!initialized)
	{
		int i;

		initialized = true;
		memset( texture, 0, sizeof(texture) );
		for (i = 0; i < ELEMENTS_OF( k_preferred_texture ); i++)
		{
			texture[k_preferred_texture[i][0]] = k_preferred_texture[i][1];
		}
	}
	return texture[terrain];
}

static int get_alternate_texture( t_terrain_type terrain )
{
	int texture[k_basic_terrain_count];
	bool initialized = false;

	if (!initialized)
	{
		int i;

		initialized = true;
		memset( texture, 0, sizeof(texture) );
		for (i = 0; i < ELEMENTS_OF( k_preferred_texture ); i++)
		{
			texture[k_preferred_texture[i][0]] = k_preferred_texture[i][2];
		}
	}
	return texture[terrain];
}

/// ----------------------------------------------------
// frame window for combat map
// place a spot of terrain
// ----------------------------------------------------
static void place_terrain(
		t_battlefield_terrain_map &	terrain_map,
		t_map_point_2d const &		base_point,
		t_map_point_2d const &		size,
		t_terrain_type				terrain_type,
		int							terrain_subtype )
{
	int map_size = terrain_map.get_size();

	int start_row = base_point.row;
	int end_row = base_point.row + size.row;

	if ( start_row < 0 )
		start_row = 0;
	if ( end_row > map_size )
		end_row = map_size;

	t_map_point_2d point;
	for ( point.row = start_row; point.row < end_row; ++point.row )
	{
		int start_column = base_point.column;
		int end_column = base_point.column + size.column;

		int row_start = terrain_map.get_tile_row_start( point.row );
		int row_end = terrain_map.get_tile_row_end( point.row );

		if ( start_column < row_start )
			start_column = row_start;
		if ( end_column > row_end )
			end_column = row_end;

		for ( point.column = start_column; point.column < end_column; ++point.column )
		{
			t_battlefield_terrain_map::t_tile & tile = terrain_map.get_tile( point );
			tile.set_terrain( terrain_type );
			tile.set_terrain_subtype( terrain_subtype );
		}
	}
}

/// ----------------------------------------------------
// frame window for combat map
// place a spot of terrain
// ----------------------------------------------------
static void place_terrain( t_battlefield_terrain_map &	terrain_map,
						   t_map_point_2d const &		base_point,
						   t_map_point_2d const &		size,
						   int const*                   terrain_weight )
{
	int				map_size = terrain_map.get_size();
	int				texture;
	t_terrain_type	terrain;
	int				start_row = base_point.row;
	int				end_row = base_point.row + size.row;
	int				weight_total = 0;
	int				choice;
	t_map_point_2d	point;

	for (terrain = k_terrain_grass; terrain < k_basic_terrain_count; enum_incr(terrain))
	{
		weight_total += terrain_weight[terrain];
	}

	if ( start_row < 0 )
		start_row = 0;
	if ( end_row > map_size )
		end_row = map_size;
	for ( point.row = start_row; point.row < end_row; ++point.row )
	{
		int start_column	= base_point.column;
		int end_column		= base_point.column + size.column;
		int row_start		= terrain_map.get_tile_row_start( point.row );
		int row_end			= terrain_map.get_tile_row_end( point.row );

		if ( start_column < row_start )
			start_column = row_start;
		if ( end_column > row_end )
			end_column = row_end;

		for ( point.column = start_column; point.column < end_column; ++point.column )
		{
			t_battlefield_terrain_map::t_tile & tile = terrain_map.get_tile( point );

			choice = random(weight_total);

			terrain = k_basic_terrain_count;
			while (enum_decr( terrain ) > k_terrain_grass)
			{
				choice -= terrain_weight[terrain];
				if (choice < 0)
					break;
			}
			tile.set_terrain( terrain );
			texture = get_preferred_texture( terrain );
			tile.set_terrain_subtype( texture );
		}
	}
}

/// ----------------------------------------------------
// frame window for combat map
// find proper terrain for a tile, based on either terrain
/// or majority
// ----------------------------------------------------
static t_terrain_type get_terrain( t_adventure_map const&      map, 
								   t_level_map_point_2d const& point )
{
	t_terrain_type terrain = k_terrain_water_river;

	if (map.is_valid( point ))
	{
		terrain = map.get_tile( point ).get_terrain();
		if (terrain != k_terrain_water_river 
			&& terrain != k_terrain_ice_river
			&& terrain != k_terrain_water)
			return get_basic_terrain(terrain);
	}

	t_level_map_point_2d new_point;
	t_map_point_2d		 offset;
	int					 size;
	int					 total = 0;
	int					 terrain_count[k_basic_terrain_count];

	size = 1;
	memset( terrain_count, 0, sizeof( terrain_count ));
	while (total == 0)
	{
		for (offset.row = -size; offset.row <= size; offset.row++)
		{
			for (offset.column = -size; offset.column <= size; offset.column++)
			{
				new_point = point + offset;
				if (!map.is_valid( new_point ))
					continue;
				terrain = map.get_tile( new_point ).get_terrain();
				// ignore water
				if (terrain != k_terrain_water && terrain != k_terrain_water_river
					&& terrain != k_terrain_ice_river)
				{	// add weight to weight array
					terrain = get_basic_terrain( terrain );
					terrain_count[terrain]++;
					total++;
				}
			}
		}
		// if total is zero, check a larger rectangle
		size++;
	}

	t_terrain_type result = k_terrain_grass;

	for (terrain = k_terrain_grass; terrain < k_basic_terrain_count; enum_incr(terrain ))
	{
		if (terrain_count[terrain] > terrain_count[result])
			result = terrain;
	}
	return result;
}

/// ----------------------------------------------------
// frame window for combat map
// compute weights for terrain around an adventure map tile
// ----------------------------------------------------
static void get_terrain_weights( t_adventure_map const& map, int* terrain_weight,
								 t_level_map_point_2d const& map_tile, 
								 t_map_point_2d const&       sub_tile_offset )
{
	t_terrain_type terrain;

	for (terrain = t_terrain_type(0); terrain < k_basic_terrain_count; enum_incr(terrain))
	{
		terrain_weight[terrain] = 0;
	}

	t_map_rect_2d  rect;
	t_map_rect_2d  tile_rect;
	t_level_map_point_2d point;
	int			   top_width;
	int			   left_width;
	int			   size;
	int			   width;
	int            height;
	int			   weight;

	size = 1;
	point.level = map_tile.level;
	// compute rectangle size
	rect.top_left = map_tile;
	rect.top_left.row -= size >> 1;
	rect.top_left.column -= size >> 1;
	rect.top_left <<= 4;
	rect.top_left += sub_tile_offset;
	rect.bottom_right = rect.top_left;
	rect.bottom_right.column += size << 4;
	rect.bottom_right.row    += size << 4;

	tile_rect.top_left = rect.top_left >> 4;
	tile_rect.bottom_right = (rect.bottom_right + t_map_point_2d(15,15)) >> 4;

	// compute width of top edge and left edge
	top_width = 16 - (rect.top() & 15);
	left_width = 16 - (rect.left() & 15);
	height = top_width;
	for (point.row = tile_rect.top(); point.row < tile_rect.bottom(); point.row++)
	{
		width = left_width;
		for (point.column = tile_rect.left(); point.column < tile_rect.right(); point.column++)
		{
			terrain = get_terrain( map, point );
			assert( terrain >= 0 && terrain < k_basic_terrain_count);
			// add weight to weight array
			weight = width * height;
			terrain_weight[terrain] += weight;
			// if next column will be the last, use right width
			if (point.column + 2 == tile_rect.right())
				width = 16 - (left_width & 15);
			else
				width = 16;
		}
		// if next row will be the last, use bottom width
		if (point.row + 2 == tile_rect.bottom())
			height = 16 - (top_width & 15);
		else
			height = 16;
	}
}

/// ----------------------------------------------------
// frame window for combat map
// choose center point for terrain map
// ----------------------------------------------------
static t_map_point_2d choose_center_point( t_adventure_map const& map,
										   t_level_map_point_2d const& center )
{
	t_direction			 direction;
	int					 center_weights[k_basic_terrain_count];
	int					 new_weights[k_basic_terrain_count];
	t_map_point_2d       base_offset(0,0);
	t_level_map_point_2d new_center;
	t_map_point_2d		 offset(0,0);
	t_map_point_2d_list  offsets;
	int					 i;
	int				     size;

	get_terrain_weights( map, center_weights, center, t_map_point_2d(0,0) );
	for (size = 0; size <= 1 && offsets.empty(); size++)
	{
		for (base_offset.row = -size; base_offset.row <= size; base_offset.row++)
		{
			for (base_offset.column = -size; base_offset.column <= size; base_offset.column++)
			{
				new_center = center + base_offset;
				for (direction = t_direction(0); direction < k_direction_count; enum_incr( direction ))
				{
					offset = get_direction_offset( direction ) * 8;
					get_terrain_weights( map, new_weights, new_center, offset );
					for (i = k_terrain_grass; i < k_basic_terrain_count; i++)
						if (center_weights[i] != new_weights[i])
							break;
					if (i < k_basic_terrain_count)
						offsets.push_back( get_direction_offset( direction ) * 25
						                   + base_offset * 80 );
				}
			}
		}
	}
	if (offsets.empty())
		return t_map_point_2d(0,0);
	
	int choice = random( offsets.size() );
	t_map_point_2d_list::iterator index = offsets.begin();

	while (choice--)
		index++;
	return *index;
}

/// ----------------------------------------------------
// frame window for combat map
// compute terrain for map
// ----------------------------------------------------
static void choose_terrain( t_adventure_map const&		map, 
						    t_battlefield_terrain_map&	terrain_map,
							t_level_map_point_2d const&	center )
{
	int	const			 k_shift = 4 + 3;
	int	const 			 k_mask = (1 << k_shift) - 1;
	int const			 k_threshold = ((1 << k_shift) - 16) / 2;
	int					 terrain_weight[k_basic_terrain_count];
	int					 block_size = 2;
	t_map_point_2d		 size( block_size, block_size );
	int					 row_end;
	t_map_point_2d		 base_point;
	t_map_point_2d		 offset;
	t_map_point_2d		 subcell_offset;
	t_level_map_point_2d position;
	int					 map_size = terrain_map.get_size();
	t_map_point_2d       block_center( block_size << (k_shift - 1), block_size << (k_shift - 1));
	t_map_point_2d       map_center( map_size << (k_shift - 1), map_size << (k_shift - 1));

	block_center += choose_center_point( map, center ) << k_shift;
	for (base_point.row = 0; base_point.row < map_size; base_point.row += block_size)
	{
		base_point.column = terrain_map.get_tile_row_start( base_point.row );
		row_end = terrain_map.get_tile_row_end( base_point.row );
		for (; base_point.column < row_end; base_point.column += block_size)
		{
			offset = base_point << k_shift;
			offset -= map_center;
			offset += block_center;
			offset /= 80;
			offset.row += center.row << k_shift;
			offset.column += center.column << k_shift;
			position.row = offset.row >> k_shift;
			position.column = offset.column >> k_shift;
			position.level = center.level;
			subcell_offset.column = (offset.column & k_mask) - k_threshold;
			subcell_offset.row    = (offset.row & k_mask) - k_threshold;
			if (subcell_offset.column < 0)
				subcell_offset.column = 0;
			if (subcell_offset.column > 15)
			{
				subcell_offset.column = 0;
				position.column++;
			}
			if (subcell_offset.row < 0)
				subcell_offset.row = 0;
			if (subcell_offset.row > 15)
			{
				subcell_offset.row = 0;
				position.row++;
			}
			get_terrain_weights( map, terrain_weight, position, subcell_offset );
			place_terrain( terrain_map, base_point, size, terrain_weight );
		}
	}
}

// -----------------------------------------------------------------------
// create random elevation changes
// -----------------------------------------------------------------------
double const k_roughness   = 0.5;
const int    k_max_height  = 255;
// -----------------------------------------------------------------------
// update rectangle information for point
// -----------------------------------------------------------------------
static int add_point( t_battlefield_terrain_map& terrain_map, 
					  t_map_point_2d const& point, t_map_point_2d const& center,
					  int& min_height, int& max_height )
{
	int height;
	int distance;
	int row_distance;

	if (!terrain_map.is_valid( point ))
		height = k_battlefield_base_height;
	else
		height = terrain_map.get_tile_vertex( point ).get_height();
	distance = abs( center.column - point.column );
	row_distance = abs( center.row - point.row );
	if (distance < row_distance)
		distance = row_distance;
	distance <<= 4;
	if (height - distance > min_height)
		min_height = height - distance;
	if (height + distance < max_height)
		max_height = height + distance;
	return height;
}

static void choose_height( t_battlefield_terrain_map& terrain_map, 
						   t_map_rect_2d const& rect, t_map_point_2d const& point,
						   int variation )
{
	if (point.row >= terrain_map.get_size())
		return;

	int row_start = terrain_map.get_tile_vertex_row_start( point.row );
	int row_end   = terrain_map.get_tile_vertex_row_end( point.row );
	int height;

	if (point.column < row_start || point.column >= row_end)
		return;

	int distance = point.column - row_start - 2;
	int right_distance = row_end - point.column - 2;
	int min_height;
	int max_height;
	int avg_height;
	
	if (distance > right_distance)
		distance = right_distance;
	if (distance <= 0)
	{
		terrain_map.get_tile_vertex( point ).set_height( k_battlefield_base_height );
		return;
	}
	distance <<= 4;
	min_height = k_battlefield_base_height - distance;
	max_height = k_battlefield_base_height + distance;

	avg_height = add_point( terrain_map, rect.top_left, point, min_height, max_height );
	avg_height += add_point( terrain_map, rect.top_right(), point, min_height, max_height );
	avg_height += add_point( terrain_map, rect.bottom_left(), point, min_height, max_height );
	avg_height += add_point( terrain_map, rect.bottom_right, point, min_height, max_height );

	if (min_height < 0)
		min_height = 0;
	if (max_height > k_max_height)
		max_height = k_max_height;
	height = avg_height >> 1;
	if (variation > max_height - min_height)
		variation = max_height - min_height;

	t_terrain_type terrain;

	if (terrain_map.is_valid( point ))
	{
		terrain = terrain_map.get_tile( point ).get_terrain();
		if (terrain == k_terrain_swamp || terrain == k_terrain_water_river
			|| terrain == k_terrain_ice_river || terrain == k_terrain_water )
			variation = 0;
	}
	if (variation > 0)
		height += random( -variation, variation );
	height >>= 1;
	if (height < min_height)
		height = min_height;
	if (height > max_height)
		height = max_height;
	terrain_map.get_tile_vertex( point ).set_height( height );
}

// -----------------------------------------------------------------------
// create random elevation changes
// -----------------------------------------------------------------------
void set_height( t_battlefield_terrain_map& terrain_map, t_map_point_2d const& left, 
				 t_map_point_2d const& right )
{
	t_map_point_2d point = (left + right) / 2;

	if (!terrain_map.is_valid( point ))
		return;

	int row_start = terrain_map.get_tile_vertex_row_start( point.row );
	int row_end   = terrain_map.get_tile_vertex_row_end( point.row );
	int distance = point.column - row_start - 2;
	int right_distance = row_end - point.column - 2;
	int min_height;
	int max_height;
	
	if (distance > right_distance)
		distance = right_distance;
	if (distance <= 0)
	{
		terrain_map.get_tile_vertex( point ).set_height( k_battlefield_base_height );
		return;
	}

	distance <<= 4;
	min_height = k_battlefield_base_height - distance;
	max_height = k_battlefield_base_height + distance;

	int height;

	if (terrain_map.is_valid( left ))
		height = terrain_map.get_tile_vertex( left ).get_height();
	else
		height = k_battlefield_base_height;
	if (terrain_map.is_valid( right ))
		height += terrain_map.get_tile_vertex( right ).get_height();
	else
		height += k_battlefield_base_height;
	height >>= 1;

	if (height < min_height)
		height = min_height;
	if (height > max_height)
		height = max_height;
	terrain_map.get_tile_vertex( point ).set_height( height );
}

// -----------------------------------------------------------------------
// create random elevation changes
// -----------------------------------------------------------------------
namespace
{
	struct t_elevation_rect : public t_map_rect_2d
	{
		double variation;
	};
};

static void randomize_elevation( t_battlefield_terrain_map& terrain_map )
{
	// Randomly perturb the vertex heights
	int            terrain_map_size = terrain_map.get_size();

	std::vector<t_elevation_rect> rects;
	t_elevation_rect              rect;
	t_elevation_rect              new_rect;
	t_map_point_2d				  center;
	int                           variation;
	int                           row_start;
	int                           row_end;

	for (center.row = 0; center.row < terrain_map_size; center.row++)
	{
		row_start = terrain_map.get_tile_vertex_row_start( center.row );
		row_end = terrain_map.get_tile_vertex_row_end( center.row );
		for (center.column = row_start; center.column < row_end; center.column++)
			terrain_map.get_tile_vertex( center ).set_height( k_battlefield_base_height );
	}

	rect.top_left = t_map_point_2d( 0, 0 );
	rect.bottom_right = t_map_point_2d( terrain_map_size, terrain_map_size );
	rect.variation = 800;
	rects.push_back( rect );
	center = (rect.top_left + rect.bottom_right) / 2;
	while (rects.size() > 0)
	{
		rect = rects.back();
		rects.pop_back();
		new_rect = rect;
		new_rect.variation = rect.variation * k_roughness;
		variation = rect.variation;
		center = ( rect.top_left + rect.bottom_right ) / 2;

		set_height( terrain_map, rect.top_left, rect.top_right() );
		set_height( terrain_map, rect.top_left, rect.bottom_left() );
		set_height( terrain_map, rect.top_right(), rect.bottom_right );
		set_height( terrain_map, rect.bottom_left(), rect.bottom_right );
		if (center.row != rect.top_left.row && center.column != rect.top_left.column)
			choose_height( terrain_map, rect, center, variation );

		// upper left corner
		new_rect.bottom_right = center;
		if (new_rect.height() > 1 || new_rect.width() > 1)
			rects.push_back( new_rect );
		// upper right corner
		new_rect.top_left.row = rect.top_left.row;
		new_rect.top_left.column = center.column;
		new_rect.bottom_right.row = center.row;
		new_rect.bottom_right.column = rect.bottom_right.column;
		if (new_rect.height() > 1 || new_rect.width() > 1)
			rects.push_back( new_rect );
		// lower right corner
		new_rect.top_left = center;
		new_rect.bottom_right = rect.bottom_right;
		if (new_rect.height() > 1 || new_rect.width() > 1)
			rects.push_back( new_rect );
		// lower left corner
		new_rect.top_left.row = center.row;
		new_rect.top_left.column = rect.top_left.column;
		new_rect.bottom_right.row = rect.bottom_right.row;
		new_rect.bottom_right.column = center.column;
		if (new_rect.height() > 1 || new_rect.width() > 1)
			rects.push_back( new_rect );
	}
}

// -----------------------------------------------------------------------
// place alternate texture dots
// -----------------------------------------------------------------------
static void place_alternate_texture( t_battlefield_terrain_map& map )
{
	t_map_point_2d point;
	int			   row_end;
	t_terrain_type terrain;
	int			   texture;

	point.row = map.get_size();
	while (point.row--)
	{
		point.column = map.get_tile_row_start( point.row );
		row_end = map.get_tile_row_end( point.row );
		while (point.column < row_end)
		{
			if (random(100) < 15)
			{
				terrain = map.get_tile( point ).get_terrain();
				texture = get_alternate_texture( terrain );
				map.get_tile( point ).set_terrain_subtype( texture );
			}
			point.column++;
		}
	}
}

// -----------------------------------------------------------------------
// draw a river in combat
// check if all tiles are snow or ice
// -----------------------------------------------------------------------
static bool is_all_snow( t_battlefield_terrain_map& map, t_map_point_2d const& origin,
						 int size )
{
	t_map_rect_2d  rect;
	t_map_point_2d point;
	int			   end_row;
	int			   start_column;
	int			   column_limit;
	int			   end_column;
	t_terrain_type terrain;

	point.row = origin.row;
	end_row = origin.row + size;
	if (point.row < 0)
		point.row = 0;
	if (end_row > map.get_size())
		end_row = map.get_size();
	for (; point.row < end_row; point.row++)
	{
		point.column = origin.column;
		end_column = point.column + size;
		start_column = map.get_tile_row_start( point.row );
		if (point.column < start_column)
			point.column = start_column;
		column_limit = map.get_tile_row_end( point.row );
		if (end_column > column_limit)
			end_column = column_limit;
		for (; point.column < end_column; point.column++)
		{
			terrain = map.get_tile(point).get_terrain();
			if (terrain != k_terrain_snow && terrain != k_terrain_ice_river)
				return false;
		}
	}
	return true;
}

// -----------------------------------------------------------------------
// draw a river in combat
// -----------------------------------------------------------------------
static void draw_river( t_battlefield_terrain_map& map, t_map_point_2d const& start_point,
					    t_map_point_2d const& stop_point )
{
	t_map_point_2d      point = start_point;
	t_map_point_2d      dest;
	t_map_point_2d      delta;
	t_map_point_2d      perpendicular;
	t_map_point_2d      mid_point;
	t_map_point_2d_list list;
	t_map_point_2d_list river_list;
	t_terrain_type      river_type = k_terrain_water_river; // k_terrain_ice_river;
	int					length;
	int					displacement;

	list.push_back( stop_point );
	while (!list.empty() )
	{
		dest = list.back();
		delta = dest - point;
		if (abs(delta.column) < 2 && abs(delta.row) < 2)
		{
			t_map_rect_2d rect;
			
			river_list.push_back( point );
			if (river_type == k_terrain_ice_river
				&& !is_all_snow( map, point, 2 ))
				river_type = k_terrain_water_river;
			point = dest;
			list.pop_back();
			continue;
		}
		delta = delta / 2;
		length = sqrt( (double)(delta.row * delta.row + delta.column * delta.column) );
		displacement = length / 2;
		if (displacement > 0)
			displacement = random( -displacement, displacement );
		if (displacement != 0)
		{
			perpendicular.row = -delta.column * displacement / length;
			perpendicular.column = delta.row * displacement / length;
			delta += perpendicular;
		}
		mid_point = point + delta;
		list.push_back( mid_point );
	}
	while (!river_list.empty())
	{
		place_terrain( map, river_list.front(), t_map_point_2d(2,2), river_type, 0 );
		river_list.pop_front();
	}
}

// -----------------------------------------------------------------------
// draw a river in combat
// check if adventure tile is river
// -----------------------------------------------------------------------
static bool is_river( t_adventure_map const& map, t_level_map_point_2d const& point )
{
	t_level_map_point_2d new_point;
	t_terrain_type       terrain;
	t_direction          direction;
	int					 i;

	terrain = map.get_tile( point ).get_terrain();
	if (terrain != k_terrain_water && terrain != k_terrain_water_river
		&& terrain != k_terrain_ice_river)
		return false;

	// find adjacent river
	direction = t_direction(0);
	for (i = 0; i < 8; i++)
	{
		new_point = point + get_direction_offset( direction );
		if (!map.is_valid( new_point ))
			return false;
		terrain = map.get_tile( new_point ).get_terrain();
		if (terrain == k_terrain_water || terrain == k_terrain_water_river
			|| terrain == k_terrain_ice_river)
			break;
		direction = clockwise( direction );
	}
	if (i == 8)
		return false;
	// check tiles next to river exit
	direction = clockwise( direction );
	new_point = point + get_direction_offset( direction );
	if (!map.is_valid( new_point ))
		return false;
	terrain = map.get_tile( new_point ).get_terrain();
	if (terrain == k_terrain_water || terrain == k_terrain_water_river
		|| terrain == k_terrain_ice_river)
		return false;
	// look to tile on left
	direction = counter_clockwise( direction, 2 );
	new_point = point + get_direction_offset( direction );
	if (!map.is_valid( new_point ))
		return false;
	terrain = map.get_tile( new_point ).get_terrain();
	if (terrain == k_terrain_water || terrain == k_terrain_water_river
		|| terrain == k_terrain_ice_river)
		return false;
	// only the center tile was river, so this is a river exit
	return true;
}

// -----------------------------------------------------------------------
// check if there is a river adjacent to this tile
// -----------------------------------------------------------------------
static bool river_is_adjacent( t_adventure_map const& map, 
							   t_level_map_point_2d const& center )
{
	t_map_point_2d		 offset;
	t_level_map_point_2d point;

	for (offset.row = -1; offset.row <= 1; offset.row++)
	{
		for (offset.column = -1; offset.column <= 1; offset.column++)
		{
			point = center + offset;
			if (!map.is_valid( point ))
				continue;
			if (is_river( map, point))
				return true;
		}
	}
	return false;
}

// -----------------------------------------------------------------------
// draw a river in combat
// -----------------------------------------------------------------------
static void create_river( t_battlefield_terrain_map& map )
{
	t_map_point_2d start;
	t_map_point_2d end;
	int			   size = map.get_size();

	start.row = random( size );
	if (start.row < size / 4)
		end.row = random( size / 4, size - 1 );
	else if (start.row > size * 3 / 4 )
		end.row = random( 0, size * 3 / 4 );
	else
		end.row = random( size );

	start.column = map.get_tile_row_start( start.row );
	end.column = map.get_tile_row_end( end.row );
	draw_river( map, start, end );
}

// -----------------------------------------------------------------------
// t_combat_window class
// -----------------------------------------------------------------------
void t_combat_window::create_battlefield( )
{
	t_creature_array&				attacker = *m_context->get_attacker();
	t_creature_array&				defender = *m_context->get_defender();
	t_adventure_map&				map = *m_context->get_adventure_map();
	t_adv_map_point const&		location = m_context->get_location();
	t_battlefield_terrain_map&	terrain_map = *m_terrain_map;
	int							terrain_map_size = terrain_map.get_size();
	t_terrain_type				terrain;
	t_adventure_tile const*		tile;
	bool						has_river;
	int							map_size = terrain_map.get_size();

	t_preset_battle_map_array	boat_resources;
	bool						boat_combat   = attacker.is_boat() || defender.is_boat();

	get_preset_maps( boat_resources, attacker, defender, location );
	////////////////////////////////////////////////////////////////////////////
	
	tile	= &map[location];
	terrain = tile->get_terrain();
	
	if (boat_combat == false)
	{
		if (location.on_bridge 
			&& (tile->get_terrain() == k_terrain_water 
				|| tile->get_terrain() == k_terrain_water_river))
		{
			place_terrain( terrain_map, t_map_point_2d(0,0), t_map_point_2d(map_size,map_size),
						   k_terrain_water_river, 0 );
		}
		else
		{
			choose_terrain( map, terrain_map, location );
			place_alternate_texture( terrain_map );
		}
		if (!location.on_bridge)
		{
		
			has_river = (m_context->get_town() == 0) && river_is_adjacent( map, location );
			if (has_river)
				create_river( terrain_map );
		
			randomize_elevation( terrain_map );
		
			if (has_river)
				terrain_map.set_river_height();
		}
	}

	t_screen_point window_size = get_parent()->get_client_rect().bottom_right();
	
	m_battlefield = new t_battlefield( *m_context, window_size, terrain_map, this, 
										boat_resources, boat_combat );
	if (boat_combat == false)
		terrain_map.establish_transitions();
	initialize();
	m_battlefield_window = new t_battlefield_window( t_screen_point(0,0), this, m_battlefield, 
													 terrain_map, boat_resources,
													 !boat_combat );
	m_battlefield_window->remove_underlays();
	m_battlefield_window->move_to_back();
}

void t_combat_window::place_combatants()
{
	t_creature_array&	attacker = *m_context->get_attacker();
	t_creature_array&	defender = *m_context->get_defender();
	t_player*			attacking_player = attacker.get_owner();
	t_formation_type	attacker_formation = attacker.get_formation();
	t_formation_type	defender_formation = defender.get_formation();
	t_player*			defending_player = defender.get_owner();
	t_town*				town = m_context->get_town();

	switch (defender.get_castle_level())
	{
		case k_town_image_village:
			if (m_context->get_location().on_bridge)
			{
				defender_formation = k_formation_bridge;
				attacker_formation = k_formation_bridge;
			}
			break;

		case k_town_image_fort:
		case k_town_image_citadel:
			defender_formation = k_formation_fort;
			break;

		case k_town_image_castle:
			defender_formation = k_formation_castle;
			break;
	}
	if ( (attacking_player == NULL) || attacking_player->is_computer() )
		auto_arrange( attacker, defender );

	place_army( *m_battlefield, attacker, attacker_formation, 
				(attacking_player == NULL) ? k_player_gray : attacking_player->get_color(), 
				false, attacking_player );

	if ( (defending_player == NULL) || defending_player->is_computer() )
		auto_arrange( defender, attacker );
	
	place_army( *m_battlefield, defender, defender_formation,
				(defending_player == NULL) ? k_player_gray : defending_player->get_color(), 
				true, defending_player );

	m_battlefield->set_influences();
}

// -----------------------------------------------------------------------
// t_combat_window class
// -----------------------------------------------------------------------
void t_combat_window::auto_combat_click( t_button* button )
{
	assert( m_battlefield );
	
	m_battlefield->set_auto_combat( true, button->is_pressed() );
}

// -----------------------------------------------------------------------
// t_combat_window class
// -----------------------------------------------------------------------
void t_combat_window::cast_spell_click( t_button* button )
{
	m_battlefield->open_spellbook( this );
}

// -----------------------------------------------------------------------
// t_combat_window class
// -----------------------------------------------------------------------
static t_button_cache const k_auto_combat_button( "button.combat.auto" );
static t_button_cache const k_cancel_spell_button( "button.combat.cancel_spell" );
static t_button_cache const k_cast_spell_button( "button.combat.cast_spell" );
static t_button_cache const k_defend_button( "button.combat.defend" );
static t_button_cache const k_melee_button( "button.combat.melee" );
static t_button_cache const k_ranged_button( "button.combat.shoot" );
static t_button_cache const k_move_button( "button.combat.move" );
static t_button_cache const k_strike_and_return_button( "button.combat.strike_and_return" );
static t_button_cache const k_options_button( "button.combat.options" );
static t_button_cache const k_retreat_button( "button.combat.retreat" );
static t_button_cache const k_surrender_button( "button.combat.surrender" );
static t_button_cache const k_wait_button( "button.combat.wait" );

void t_combat_window::create_buttons( t_window* background )
{
	t_screen_point      origin = background->get_screen_rect().top_left();
	t_screen_point      point;
	t_help_block const& help = get_help_block( "combat_screen" );

	origin = to_client( origin );

	point = m_bitmaps->find( "auto_attack" )->get_rect().top_left() - origin;
	m_auto_combat_button = new t_toggle_button( k_auto_combat_button.get(), point, background );
	m_auto_combat_button->set_click_handler( bound_handler( *this, 
		                                     &t_combat_window::auto_combat_click ));
	set_help( m_auto_combat_button, help, "auto_combat" );
	
	point = m_bitmaps->find( "cast_spell" )->get_rect().top_left() - origin;
	m_cast_spell_button = new t_button( k_cast_spell_button.get(), point, background );
	m_cast_spell_button->set_click_handler( bound_handler( *this, 
		                                    &t_combat_window::cast_spell_click ));
	set_help( m_cast_spell_button, help, "cast_spell" );

	m_cancel_spell_button = new t_button( k_cancel_spell_button.get(), point, background );
	m_cancel_spell_button->set_click_handler( bound_handler( *this, 
		                                      &t_combat_window::cancel_spell_click ));
	set_help( m_cancel_spell_button, help, "cancel_spell" );
	m_cancel_spell_button->set_visible( false );

	point = m_bitmaps->find( "defend" )->get_rect().top_left() - origin;
	m_defend_button = new t_button( k_defend_button.get(), point, background );
	m_defend_button->set_click_handler( bound_handler( *this,
		                                &t_combat_window::defend_click ));
	set_help( m_defend_button, help, "defend" );
	
	point = m_bitmaps->find( "combat_options" )->get_rect().top_left() - origin;
	m_options_button = new t_button( k_options_button.get(), point, background );
	m_options_button->set_click_handler( bound_handler( *this, &t_combat_window::options_click ));
	set_help( m_options_button, help, "options" );

	point = m_bitmaps->find( "retreat" )->get_rect().top_left() - origin;
	m_retreat_button = new t_button( k_retreat_button.get(), point, background );
	m_retreat_button->set_click_handler( bound_handler( *this, &t_combat_window::retreat_click ));
	set_help( m_retreat_button, help, "retreat" );
	
	point = m_bitmaps->find( "surrender" )->get_rect().top_left() - origin;
	m_surrender_button = new t_button( k_surrender_button.get(), point, background );
	m_surrender_button->set_click_handler( bound_handler( *this, &t_combat_window::surrender_click ));
	set_help( m_surrender_button, help, "surrender" );	

	point = m_bitmaps->find( "wait" )->get_rect().top_left() - origin;
	m_wait_button = new t_button( k_wait_button.get(), point, background );
	m_wait_button->set_click_handler( bound_handler( *this, &t_combat_window::wait_click ));
	set_help( m_wait_button, help, "wait" );	

	point = m_bitmaps->find( "melee" )->get_rect().top_left() - origin;
	m_combat_mode_button[k_combat_mode_melee] = new t_button( k_melee_button.get(), point, background );
	set_help( m_combat_mode_button[k_combat_mode_melee], help, "melee_mode" );

	m_combat_mode_button[k_combat_mode_strike_and_return]
		= new t_button( k_strike_and_return_button.get(), point, background );
	set_help( m_combat_mode_button[k_combat_mode_strike_and_return], help,
		      "strike_and_return_mode" );

	m_combat_mode_button[k_combat_mode_ranged] = new t_button( k_ranged_button.get(), point, background );
	set_help( m_combat_mode_button[k_combat_mode_ranged], help, "ranged_mode" );

	m_combat_mode_button[k_combat_mode_move] = new t_button( k_move_button.get(), point, background );
	set_help( m_combat_mode_button[k_combat_mode_move], help, "cursor_mode" );

	m_combat_mode_button[k_combat_mode_spell] = new t_button( k_cast_spell_button.get(), point, background );
	set_help( m_combat_mode_button[k_combat_mode_spell], help, "spell_mode" );

	t_button_handler     handler;
	t_combat_cursor_mode mode;

	handler = bound_handler( *m_battlefield, &t_battlefield::cursor_mode_menu );
	for (mode = t_combat_cursor_mode(0); mode < k_combat_mode_count; enum_incr( mode ))
		m_combat_mode_button[mode]->set_click_handler( handler );

	set_cursor_mode( k_combat_mode_ranged );
}

// -----------------------------------------------------------------------
// t_combat_window class
// -----------------------------------------------------------------------
void t_combat_window::create_displays( t_window* background )
{
	t_screen_rect  rect;
	t_screen_point origin = background->get_screen_rect().top_left();
	t_help_block const& help = get_help_block( "combat_screen" );

	origin = to_client( origin );

	t_bitmap_layer const* layer;

	// create creature display
	layer = m_bitmaps->find( "creature_icon" );
	rect = layer->get_rect() - origin;
	m_creature_icon = new t_combat_creature_icon( rect, background, this );

	// Create info windows for "ready" creatures
	layer = m_bitmaps->find( "health_icon" );
	if ( layer )
	{
		m_health_icon = new t_bitmap_layer_window( layer, -origin, background );
		set_help( m_health_icon, help, "hits_left" );
	}
	else
	{
		m_health_icon = NULL;
	}

	layer = m_bitmaps->find( "health_text" );
	if ( layer )
	{
		m_health_text = new t_text_window( get_font( 14 ), layer->get_rect() - origin, background, "", t_pixel_24(0,0,0) );
		m_health_text->set_center_horizontal();
		m_health_text->set_drop_shadow( false );
		set_help( m_health_text, help, "hits_left" );
	}
	else
	{
		m_health_text = NULL;
	}

	layer = m_bitmaps->find( "spell_points_icon" );
	if ( layer )
	{
		m_spell_points_icon = new t_bitmap_layer_window( layer, -origin, background );
		set_help( m_spell_points_icon, help, "spell_points_left" );
	}
	else
	{
		m_spell_points_icon = NULL;
	}

	layer = m_bitmaps->find( "spell_points_text" );
	if ( layer )
	{
		m_spell_points_text = new t_text_window( get_font( 14 ), layer->get_rect() - origin, background, "", t_pixel_24(0,0,0) );
		m_spell_points_text->set_center_horizontal();
		m_spell_points_text->set_drop_shadow( false );
		set_help( m_spell_points_text, help, "spell_points_left" );
	}
	else
	{
		m_spell_points_text = NULL;
	}

	layer = m_bitmaps->find( "shots_icon" );
	if ( layer )
	{
		m_shots_icon = new t_bitmap_layer_window( layer, -origin, background );
		set_help( m_shots_icon, help, "shots_left" );
	}
	else
	{
		m_shots_icon = NULL;
	}

	layer = m_bitmaps->find( "shots_text" );
	if ( layer )
	{
		m_shots_text = new t_text_window( get_font( 14 ), layer->get_rect() - origin, background, "", t_pixel_24(0,0,0) );
		m_shots_text->set_center_horizontal();
		m_shots_text->set_drop_shadow( false );
		set_help( m_shots_text, help, "shots_left" );
	}
	else
	{
		m_shots_text = NULL;
	}

	int i;

	for ( i = 0; i < k_max_spell_icons; i++ )
	{
		std::string name = format_string( "spell_%d", i + 1 );
		layer = m_bitmaps->find( name );

		if ( layer )
		{
			rect = layer->get_rect() - origin;
			m_active_spells[i] = new t_spell_icon_window( k_spell_none, rect, background );
		}
		else
		{
			m_active_spells[i] = NULL;
		}
	}

	// Create text window for actions
	layer = m_bitmaps->find( "action_text" );
	m_action_text_window = new t_text_window( get_font( 14 ), layer->get_rect() - origin, background, "", t_pixel_24(0,0,0) );
	m_action_text_window->set_center_horizontal();
	m_action_text_window->set_drop_shadow( false );
	m_action_text_window->set_visible( false );
}

// -----------------------------------------------------------------------
// t_combat_window class
// Set the visibility of the ready creature status icons and the action
// text, depending on the state of the game
// -----------------------------------------------------------------------
void t_combat_window::update_status_icons()
{
	bool ready_icons_visible = ( m_action_displayer_list.empty() );
	bool ready_text_visible  = ( m_ready_creature != NULL && m_action_displayer_list.empty() );
	bool action_text_visible = ( !m_action_displayer_list.empty() );

	if ( action_text_visible )
	{
		assert ( m_action_message.is_displayable() );
		m_creature_icon->set_creature( m_action_message.get_creature(), true );
	}
	else if ( ready_text_visible )
	{
		m_creature_icon->set_creature( m_ready_creature );
	}
	else
	{
		m_creature_icon->clear_creature();
	}

	if ( m_health_icon )
		m_health_icon->set_visible( ready_icons_visible );

	if ( m_health_text )
		m_health_text->set_visible( ready_text_visible );

	if ( m_spell_points_icon )
		m_spell_points_icon->set_visible( ready_icons_visible );

	if ( m_spell_points_text )
		m_spell_points_text->set_visible( ready_text_visible );

	if ( m_shots_icon )
		m_shots_icon->set_visible( ready_icons_visible );

	if ( m_shots_text )
		m_shots_text->set_visible( ready_text_visible );

	int i;
	for ( i = 0; i < k_max_spell_icons; i++ )
	{
		if ( m_active_spells[i] )
		{
			if ( m_active_spells[i]->get_spell() == k_spell_none )
				m_active_spells[i]->set_visible( false );
			else
				m_active_spells[i]->set_visible( ready_text_visible );
		}
	}

	m_action_text_window->set_visible( action_text_visible );
}

extern  t_external_string const k_text_infinity_abbreviation;
extern  t_external_string const k_text_shots;
extern  t_external_string const k_text_not_applicable;

// -----------------------------------------------------------------------
// t_combat_window class
// Display the 'ready' creature ( whilst no animation is playing )
// -----------------------------------------------------------------------
void t_combat_window::display_creature_ready( t_combat_creature const * creature )
{
	std::string text;

	m_ready_creature = creature;

	if ( creature )
	{
		if ( m_health_text )
		{
			text = format_string( "%d", creature->get_remaining_hitpoints() );

			m_health_text->set_text( text );
		}

		if ( m_spell_points_text )
		{
			text = format_string( "%d", creature->get_spell_points() );

			m_spell_points_text->set_text( text );
		}

		if ( m_shots_text )
		{
			if (creature->has_ability( k_ability_ranged ))
			{
				if (creature->has_ability( k_ability_unlimited_shots ))
				{
					text = k_text_infinity_abbreviation;
				}
				else
				{
					text = format_string( " %i ", creature->get_shots() );
					text += k_text_shots;
				}
			}
			else
			{
				text = k_text_not_applicable;
			}

			m_shots_text->set_text( text );
		}

		t_spell_list const & spell_list = creature->get_active_spells();
		t_spell_list::const_iterator spell_iter = spell_list.begin();
		int spell_icon_index = 0;

		while ( spell_iter != spell_list.end() && spell_icon_index != k_max_spell_icons )
		{
			if ( m_active_spells[spell_icon_index] )
			{
				m_active_spells[spell_icon_index]->set_spell( *spell_iter );
				spell_iter++;
			}
			spell_icon_index++;
		}

		while ( spell_icon_index != k_max_spell_icons )
		{
			if ( m_active_spells[spell_icon_index] )
			{
				m_active_spells[spell_icon_index]->set_spell( k_spell_none );
			}
			spell_icon_index++;
		}
	}

	update_status_icons();
}

// -----------------------------------------------------------------------
// Display a message about the current action on the screen.
// -----------------------------------------------------------------------
void t_combat_window::display_creature_action( t_combat_action_message_displayer & displayer, t_combat_action_message const & message )
{
	if ( !message.is_displayable() )
		return;

	if ( !m_action_displayer_list.empty() )
	{
		// In order to avoid having the action display flicker, we allow animations to lock
		// the display when they start. The animations can only get the lock when they start up.
		// This ensures that most action messages are displayed for the length of an animation,
		// though a higher-priority message may still cause flicker...

		// However, two animations with exactly the same message and creature can both hold locks
		// on the display at the same time, since often 1 visual effect is made of several
		// animations.
		if (   m_action_message.get_creature() != message.get_creature()
			|| m_action_message.get_text() != message.get_text() )
		{
			if ( message.get_priority() <= m_action_message.get_priority() )
			{
				return;
			}
			else
			{
				m_action_displayer_list.clear();
			}
		}
		else
		{
			assert( message.get_priority() == m_action_message.get_priority() );
		}
	}

	m_action_displayer_list.push_back( &displayer );
	m_action_message = message;
	m_action_text_window->set_text( message.get_text() );

	update_status_icons();
}

// -----------------------------------------------------------------------
// The given displayer is done displaying its message
// -----------------------------------------------------------------------
void t_combat_window::end_displayed_creature_action( t_combat_action_message_displayer & displayer )
{
	// Remove lock if displayer holds one
	t_combat_action_message_displayer_list::iterator it;

	for ( it = m_action_displayer_list.begin(); it != m_action_displayer_list.end() && *it != &displayer; it++ )
	{
	}

	if ( it == m_action_displayer_list.end() )
		return;


	m_action_displayer_list.erase( it );

	if ( m_action_displayer_list.empty() )
	{
		// Last lock, return to ready creature
		m_action_message.clear();
		update_status_icons();
	}
}

// -----------------------------------------------------------------------
// t_combat_window class
// -----------------------------------------------------------------------
void t_combat_window::set_cursor_mode( t_combat_cursor_mode mode )
{
	int i;

	for (i = 0; i < k_combat_mode_count; i++)
		m_combat_mode_button[i]->set_visible( mode == i );
}

// -----------------------------------------------------------------------
// t_combat_window class
// -----------------------------------------------------------------------
void t_combat_window::defend_click( t_button* button )
{
	// Do the job locally
	m_battlefield->defend();
}

// -----------------------------------------------------------------------
// t_combat_window class
// -----------------------------------------------------------------------
void t_combat_window::cancel_spell_click( t_button* )
{
	m_battlefield->cancel_spell();
}

// -----------------------------------------------------------------------
// t_combat_window class
// -----------------------------------------------------------------------
void t_combat_window::melee_click( t_button* button )
{
}

// ----------------------------------------------------
// frame window for adventure map
// new game clicked
// ----------------------------------------------------
void t_combat_window::new_game()
{
	if (m_battlefield->get_state() != k_combat_state_awaiting_human_order)
		return;

	t_adventure_map_ptr new_map = read_new_game( this );

	if (new_map == 0)
	{
		return;
	}

	// save self-reference.
	t_adventure_frame*	adventure_frame = m_context->get_adventure_frame();
	t_window_ptr		ref = this;	

	close();
	adventure_frame->close();
	launch_new_game( new_map );
}

// ----------------------------------------------------
// frame window for adventure map
// new game clicked
// ----------------------------------------------------
void t_combat_window::load_game()
{
	if (m_battlefield->get_state() != k_combat_state_awaiting_human_order)
		return;

	t_adventure_map_ptr new_map = read_saved_game( this );

	if (new_map == 0)
	{
		return;
	}

	// save self-reference.
	t_window_ptr		ref = this;
	t_adventure_frame*	adventure_frame = m_context->get_adventure_frame();

	close();
	adventure_frame->close();
	launch_saved_game( new_map );
}

static t_external_string const k_text_save_not_supported( "save_not_supported.misc" );
bool t_combat_window::save_game()
{
	if (m_battlefield->get_state() != k_combat_state_awaiting_human_order)
		return false;

	if (!m_context->save_allowed())
	{
		ok_dialog( k_text_save_not_supported );
		return false;
	}

	t_saved_combat	saved_combat( *this );

	return write_saved_game( this, m_context->get_adventure_map(), &saved_combat );
}

// ----------------------------------------------------
// change object's model to tattooed white trash (Adam)
// ----------------------------------------------------
static t_external_string const k_text_not_a_hero( "not_a_hero.misc" );

static void hero_model_cheat( t_battlefield& battlefield, t_cheating_state cheat_state )
{
	if (battlefield.get_state() != k_combat_state_awaiting_human_order)
		return;

	t_combat_creature*	creature = battlefield.get_acting_creature();
	t_hero*				hero = creature->get_hero();

	if (hero == 0)
	{
		ok_dialog( k_text_not_a_hero, true );
		return;
	}

	t_combat_actor_model_ptr model;

	switch (cheat_state)
	{
		case k_cheating_adam:
			model = get_adam_model( battlefield.get_model_scale() );
			break;

		case k_cheating_jack:
			model = get_jack_model( battlefield.get_model_scale() );
			break;
	}

	creature->set_cheating( cheat_state );
	battlefield.change_model( *creature, model );
}

// ----------------------------------------------------
// t_combat_window class
// handle a menu click
// ----------------------------------------------------
bool t_combat_window::menu_click( int id )
{
	bool const is_defender = m_battlefield->get_acting_creature()->get_controller();

	switch (id)
	{
		case k_id_file_new_scenario:
			new_game();
			return true;

		case k_id_file_load_game:
			load_game();
			return true;

		case k_id_file_save_game:
			save_game();
			return true;

		case k_id_file_exit:
			quit();
			return true;

		case k_id_cheat_lose_combat:
			if (is_defender)
				force_combat_termination(k_result_defender_lost);
			else
				force_combat_termination(k_result_attacker_lost);
			return true;

		case k_id_cheat_win_combat:
			if (is_defender)
				force_combat_termination(k_result_attacker_lost);
			else
				force_combat_termination(k_result_defender_lost);
			return true;

		case k_id_cheat_win_map:
		case k_id_cheat_lose_map:
			{
				t_window_ptr this_ptr = this;
				t_adventure_frame* frame = m_context->get_adventure_frame();
				if( frame )
				{
					frame->game_over(
						id == k_id_cheat_win_map ? k_game_over_result_win : k_game_over_result_lose,
						frame->get_map()->get_player().get_team() );
				}
			}
			return true;

		case k_id_cheat_tattooed_white_trash:
			hero_model_cheat( *m_battlefield, k_cheating_adam );
			return true;

		case k_id_cheat_old_man_jack:
			hero_model_cheat( *m_battlefield, k_cheating_jack );
			break;
			
		default:
			break;
	}

	return t_window::menu_click( id );
}

// -----------------------------------------------------------------------
// t_combat_window class
// -----------------------------------------------------------------------
void t_combat_window::options_click( t_button* button )
{
	t_window_ptr		ref = this;

	t_counted_ptr<t_dialog_combat_options> dialog;

	dialog = new t_dialog_combat_options( this );
	dialog->run_modal();

	if ( is_open() )
	{
		if ( get_show_combat_movement_shading() && m_battlefield->get_current_movement_shaded_creature() )
		{
			m_battlefield->set_movement_shading( *m_battlefield->get_current_movement_shaded_creature() );
		}
		else
		{
			m_battlefield->remove_movement_shading();
		}

		m_battlefield_window->set_grid_shading( get_show_combat_grid() );
	}
}

// -----------------------------------------------------------------------
// t_combat_window class
// -----------------------------------------------------------------------
static t_external_string const k_text_concede( "concede.misc" );
void t_combat_window::retreat_click( t_button* button )
{
	bool const  is_defender = m_battlefield->get_acting_side();
	std::string text;

	if (m_battlefield->can_retreat( is_defender, &text ))
	{
		if (yes_no_dialog(text, true))
		{
			// pick a town to retreat to assuming there is one...
			// set the combat result to retreated
			if (is_defender)
				force_combat_termination(k_result_defender_retreated);
			else
				force_combat_termination(k_result_attacker_retreated);
		}
	}
	else
	{
		ok_dialog( text, true);
	}
}

// -----------------------------------------------------------------------
// t_combat_window class
// -----------------------------------------------------------------------
void t_combat_window::surrender_click( t_button* button )
{
	bool const	is_defender = m_battlefield->get_acting_side();
	std::string	text;

	if (!m_battlefield->can_surrender( is_defender, &text ))
	{
		ok_dialog( text, true );
		return;
	}

	if (yes_no_dialog(text, true))
	{
		// pick a town to retreat to assuming there is one...
		// set the combat result to retreated
		m_battlefield->surrender();
	}
}

// -----------------------------------------------------------------------
// t_combat_window class
// -----------------------------------------------------------------------
void t_combat_window::wait_click( t_button* button )
{
	// Do the job locally
	m_battlefield->wait_action();
}

// -----------------------------------------------------------------------
// t_combat_window class
// -----------------------------------------------------------------------
void t_combat_window::enable_all_buttons( bool arg )
{
	m_auto_combat_button->enable( arg );
	m_cancel_spell_button->enable( arg );
	m_cast_spell_button->enable( arg );
	m_defend_button->enable( arg );
	m_options_button->enable( arg );
	m_retreat_button->enable( arg );
	m_surrender_button->enable( arg );
	m_wait_button->enable( arg );

	int i;

	for (i = 0; i < k_combat_mode_count; i++)
		m_combat_mode_button[i]->enable( arg );
}

// -----------------------------------------------------------------------
// t_combat_window class
// -----------------------------------------------------------------------
void t_combat_window::enable_cast_spell( bool arg )
{
	m_cast_spell_button->enable( arg );
}

// -----------------------------------------------------------------------
// t_combat_window class
// -----------------------------------------------------------------------
void t_combat_window::enable_cancel_spell( bool arg )
{
	m_cancel_spell_button->enable( arg );
}

// -----------------------------------------------------------------------
// t_combat_window class
// -----------------------------------------------------------------------
void t_combat_window::enable_options( bool arg )
{
	m_options_button->enable( arg );
}

// -----------------------------------------------------------------------
// t_combat_window class
// -----------------------------------------------------------------------
void t_combat_window::enable_wait( bool arg )
{
	m_wait_button->enable( arg );
}

// -----------------------------------------------------------------------
// Set state for auto-combat button
// -----------------------------------------------------------------------
void t_combat_window::enable_auto_combat( bool arg )
{
	m_auto_combat_button->enable(arg);
}

// -----------------------------------------------------------------------
// show either spell or cancel spell button
//-----------------------------------------------------------------------
void t_combat_window::show_cancel_spell_button( bool arg )
{
	m_cast_spell_button->set_visible( !arg );
	m_cancel_spell_button->set_visible( arg );
}

// -----------------------------------------------------------------------
// kill all creatures belonging to a side
// -----------------------------------------------------------------------
static void kill_creatures( t_battlefield& battlefield, bool side )
{
	t_combat_creature_list::iterator	index = battlefield.creatures_begin();
	t_combat_creature_list::iterator	end = battlefield.creatures_end();
	t_combat_creature*					creature;

	for (; index != end; ++index)
	{
		creature = *index;
		if (creature->belongs_to_defender() != side)
			continue;
		creature->set_number( 0 );
	}
}

// -----------------------------------------------------------------------
// t_combat_window class
// -----------------------------------------------------------------------
void t_combat_window::force_combat_termination( t_combat_result result )
{
	if (result == k_result_attacker_lost)
		kill_creatures( *m_battlefield, false );
	if (result == k_result_defender_lost)
		kill_creatures( *m_battlefield, true );
	// Do the job locally
	set_result( result );
	m_battlefield->end_combat();
}


void t_combat_window::on_close()
{
	stop_music();
	m_ready_creature = 0;
	m_battlefield = 0;

	t_adventure_frame* frame = m_context->get_adventure_frame();
	t_adventure_map_window* window = 0;

	if (frame != 0)
		window = frame->get_map_window();
	if (window != 0)
		window->enable_animation( show_adventure_animations() );

	if( g_cheats_window != 0 && g_cheats_window->is_open() )
		g_cheats_window->close();
	g_cheats_window = 0;
}

// -----------------------------------------------------------------------
// t_combat_window class
// find nearest town other than the one at this point
// -----------------------------------------------------------------------
t_town*	t_combat_window::find_nearest_town( bool defender, 
											bool & enemy_adjacent_towns_found,
											bool & blocked_towns_found,
											t_adv_map_point * location,
											bool*  nearby_town_found ) const
{
	t_player* player = m_battlefield->get_player( defender );

	t_adv_map_point temp_location;

	if ( location == NULL )
		location = &temp_location;

	if ( player == NULL )
		return NULL;

	t_creature_array* army = &m_battlefield->get_army( defender );
	t_adventure_map* map = m_context->get_adventure_map();

	return ::find_nearest_town( map, player,  m_context->get_location(), 
								*location, 
								army, m_context->get_town(),
								&enemy_adjacent_towns_found,
								&blocked_towns_found, true, 
								k_minimum_retreat_distance, 
								nearby_town_found );
}

// -----------------------------------------------------------------------
// Return a pointer to the army (if any) created as a result of this combat
// -----------------------------------------------------------------------
t_army_ptr t_combat_window::get_new_army_ptr()
{
	return m_new_army_ptr;
}

// -----------------------------------------------------------------------
// Sets value to be returned by get_new_army_ptr()
// -----------------------------------------------------------------------
void t_combat_window::set_new_army_ptr( t_army_ptr & new_army_ptr )
{
	m_new_army_ptr = new_army_ptr;
}